package de.mmis.core.base.abstracttree.serializer.internal;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.DeserializationMethod;
import de.mmis.core.base.MethodDescriptor;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.Tree.Type;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.MethodInvocationException;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.MethodNotMatchingException;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.MethodReturnValue;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper.NoMatchingMethodFoundException;

/**
 * Deserializer class for deserializing static method calls from an {@link Tree}
 * s
 * 
 * @author Christian Koch
 */
public class StaticMethodDeserializer {

	@DeserializationMethod
	public static Object deserialize(Tree[] exps, ClassLoader classLoader,
			String id, Map<String, Object> idmap)
			throws DeserializableException {

		if (exps.length < 1)
			throw new DeserializableException(
					new InnerNode(exps),
					"StaticMethodDeserializer needs at least one parameter defining the method to be executed");

		if (exps[0].getType() != Type.LEAF)
			throw new DeserializableException(
					new InnerNode(exps),
					"first sub-Tree in struct has to be an Atom indicating the class and method name");

		String classmethod = ((LeafNode) exps[0]).getAsSymbol();

		int index = classmethod.lastIndexOf(".");
		if (index == -1)
			throw new DeserializableException(new InnerNode(exps),
					"class and method name have to be separated with a \".\" (dot)");

		String classname = classmethod.substring(0, index);
		String methodname = classmethod.substring(index + 1);

		Class<?> clazz;

		try {
			clazz = Class.forName(classname, false, classLoader);
		} catch (ClassNotFoundException e) {
			throw new DeserializableException(new InnerNode(exps), e,
					"cannot find class \"" + classname + "\" in classpath");
		}

		InnerNode methodExp = new InnerNode(new LeafNode(methodname));
		for (int i = 1; i < exps.length; i++)
			methodExp.add(exps[i]);

		ArrayList<MethodNotMatchingException> exlist = new ArrayList<MethodNotMatchingException>();
		for (Method m : clazz.getMethods()) {
			if (Modifier.isStatic(m.getModifiers())
					&& Modifier.isPublic(m.getModifiers())) {
				MethodDescriptor md = new MethodDescriptor(null, m);

				try {
					MethodReturnValue mrv = SerializerHelper
							.invokeObjectMethod(md, methodExp, null,
									classLoader, idmap);

					if (mrv != null)
						return mrv.getObject();
				} catch (MethodInvocationException e) {
					throw new DeserializableException(new InnerNode(exps), e,
							"exception occurred while invoking static method");
				} catch (MethodNotMatchingException e) {
					exlist.add(e);
				}
			}
		}
		if (exlist.isEmpty())
			throw new DeserializableException(new InnerNode(exps),
					new NoMatchingMethodFoundException("no method matching \""
							+ new InnerNode(exps) + "\" found"));

		throw new DeserializableException(new InnerNode(exps),
				new NoMatchingMethodFoundException(exlist));

	}
}
